/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: span.h
 *
 * Purpose: This section describes the ara::core::Span type that constitutes 
 *          a view over a contiguous sequence of objects, the storage of which
*           is owned by another object
 *
 * Developer:
 *   wen.gu , 2020-04-17
 *
 * TODO:
 *
 ***************************************************************************/

#ifndef __ARA_CORE_SPAN_H__
#define __ARA_CORE_SPAN_H__
/******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include <limits>
#include <cstdint>
#include <type_traits>

#include "ara/core/array.h"
#include "ara/core/utility.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/


/******************************************************************************
 **    TYPE DEFINITIONS
 ******************************************************************************/
namespace ara
{
namespace core
{
constexpr std::size_t dynamic_extent= std::numeric_limits<std::size_t>::max();
/******************************************************************************
 **    CLASSES/FUNCTIONS DEFINITIONS
 ******************************************************************************/

template <typename T, std::size_t Extent = dynamic_extent>
class Span
{
    using element_type = T;
    using value_type = typename std::remove_cv<element_type>::type;
    using index_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    using size_type = index_type;
    using pointer = element_type*;
    using const_pointer = element_type const*;
    using reference = element_type&;
    using iterator = pointer; /** todo, implementation iterator */
    using const_iterator = const_pointer; /** todo, implementation const iterator */
    using reverse_iterator = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    //constexpr index_type extent = Extent;

public:
    /** Constraints: Extent <= 0 is true. */
    constexpr Span() noexcept
        :mData(nullptr),
        mSize(0)
    {
        /** todo something */
    }

    constexpr Span(pointer ptr, index_type  count)
        :mData(ptr),
        mSize(count)
    {
        /** todo something */
    }

    constexpr Span(pointer firstElem, pointer lastElem)
        :Span(firstElem, lastElem - firstElem)
    {
        /** todo something */
    }

    template <std::size_t N, class = typename std::enable_if<((Extent == dynamic_extent) || (N == Extent)) &&
        std::is_convertible<std::remove_pointer<decltype(ara::core::data(arr))>::type(*)[], T(*)[]>::value >::type>
        constexpr Span(element_type(&arr)[N]) noexcept
        :Span(ara::core::data(arr), N)
    {
        /** todo something */
    }

    template <std::size_t N, class = typename std::enable_if<((Extent == dynamic_extent) || (N == Extent)) &&
        std::is_convertible<std::remove_pointer<decltype(ara::core::data(arr))>::type(*)[], T(*)[]>::value >::type>
        constexpr Span(Array< value_type, N >& arr) noexcept
        :Span(ara::core::data(arr), N)
    {
        /** todo something */
    }

    template <std::size_t N, class = typename std::enable_if<((Extent == dynamic_extent) || (N == Extent)) &&
        std::is_convertible<std::remove_pointer<decltype(ara::core::data(arr))>::type(*)[], T(*)[]>::value >::type>
        constexpr Span(Array< value_type, N > const& arr) noexcept
        :Span(ara::core::data(arr), N)
    {
        /** todo something */
    }

    template <typename Container, class = typename std::enable_if<((Extent == dynamic_extent) || (ara::core::size(Container) == Extent)) &&
        !internal_impl::is_template_of<Span, Container>::value &&
        !internal_impl::is_template_of<ara::core::Array, Container>::value &&
        !internal_impl::is_template_of<std::array, Container>::value &&
        !std::is_array<Container>::value &&
        (ara::core::data(Container) != nullptr) &&
        (ara::core::size(Container) > 0) &&
        std::is_convertible<std::remove_pointer<decltype(ara::core::data(Container))>::type(*)[], T(*)[]>::value >::type>
        constexpr Span(Container& cont)
        :Span(ara::core::data(Container), ara::core::size(Container))
    {
        /** todo something */
    }

    template <typename Container, class = typename std::enable_if<((Extent == dynamic_extent) || (ara::core::size(Container) == Extent)) &&
        !internal_impl::is_template_of<Span, Container>::value &&
        !internal_impl::is_template_of<ara::core::Array, Container>::value &&
        !internal_impl::is_template_of<std::array, Container>::value &&
        !std::is_array<Container>::value &&
        (ara::core::data(Container) != nullptr) &&
        (ara::core::size(Container) > 0) &&
        std::is_convertible<std::remove_pointer<decltype(ara::core::data(Container))>::type(*)[], T(*)[]>::value >::type>
        constexpr Span(Container const& cont)
        :Span(ara::core::data(Container), ara::core::size(Container))
    {
        /** todo something */
    }

    constexpr Span(Span const& other) noexcept = default;

    template <typename U, std::size_t N, class = typename std::enable_if<((Extent == dynamic_extent) || (N == Extent)) &&
        std::is_convertible< U(*)[], element_type(*)[]>::value>::type >
        constexpr Span(Span< U, N > const& s) noexcept
        :Span(s.data(), s.size())
    {
        /** todo something */
    }

    ~Span() noexcept = default; /** todo is need release pointer?? */

public:
    Span& operator= (Span const& other) noexcept = default;

    template <std::size_t Count >
    constexpr Span<element_type, Count> first() const
    {
        static_assert(Count <= size());
        return { data(), Count };
    }

    constexpr Span<element_type, dynamic_extent> first(index_type count) const
    {
        static_assert(count <= size());
        return { data(), count };
    }

    template <std::size_t Count>
    constexpr Span<element_type, Count> last() const
    {
        static_assert(Count <= size());
        return { data() + (size() - Count), Count };
    }

    constexpr Span<element_type, dynamic_extent> last(index_type count) const
    {
        static_assert(count <= size());
        return { data() + (size() - count), count };
    }

    template <std::size_t Offset, std::size_t Count = dynamic_extent>
    using subspan_return_t = Span<element_type, (Count != dynamic_extent) ? Count : ((Extent != dynamic_extent) ? (Extent - Offset) : dynamic_extent)>;

    template <std::size_t Offset, std::size_t Count = dynamic_extent >
     constexpr auto subspan() const-> subspan_return_t<Offset, Count>
    {
        static_assert((Offset <= size()) && ((Count == dynamic_extent) || (Offset + Count <= size())));
        return Span<element_type, (Count != dynamic_extent) ? Count : ((Extent != dynamic_extent) ? (Extent - Offset) : dynamic_extent) >
                (data() + Offset, (Count != dynamic_extent) ? Count : (size() - Offset));
    }


    constexpr Span<element_type, dynamic_extent> subspan(index_type offset, index_type count = dynamic_extent) const
    {
        static_assert(((offset <= size()) && ((count == dynamic_extent) || (offset + count <= size()))), "subspan(offset, count)");

        return { data() + offset, count == dynamic_extent ? size() - offset : count };
    }

public:
    constexpr index_type size() const noexcept { return mSize; }
    constexpr index_type size_bytes() const noexcept { return size() * sizeof(element_type); }

    constexpr bool empty() const noexcept { return size() == 0; }

    constexpr reference operator[] (index_type idx) const
    {
        static_assert(idx < size());
        return *(data() + idx);
    }
    constexpr pointer data() const noexcept { return mData; }

    constexpr iterator begin() const noexcept
    {
        return { data() };
    }

    constexpr iterator end() const noexcept
    {
        return { data() + size() };
    }

    constexpr const_iterator cbegin() const noexcept
    {
        return { data() };
    }

    constexpr const_iterator cend() const noexcept
    {
        return { data() + size() };
    }

    constexpr reverse_iterator rbegin() const noexcept
    {
        return reverse_iterator(end());
    }

    constexpr reverse_iterator rend() const noexcept
    {
        return reverse_iterator(begin());
    }

    constexpr const_reverse_iterator crbegin() const noexcept
    {
        return const_reverse_iterator(cend());
    }

    constexpr const_reverse_iterator crend() const noexcept
    {
        return const_reverse_iterator(cbegin());
    }
private:
    pointer mData; // exposition only
    index_type mSize; // exposition only
};

template <typename T>
constexpr Span<T> MakeSpan(T* ptr, typename Span< T >::index_type  count)
{
    return Span<T>(ptr, count);
}

template <typename T>
constexpr Span<T> MakeSpan(T* firstElem, T* lastElem)
{
    return Span<T>(firstElem, lastElem);
}

template <typename T, std::size_t N>
constexpr Span<T, N> MakeSpan(T(&arr)[N]) noexcept
{
    return { arr };
}

template <typename Container>
constexpr Span<typename Container::value_type> MakeSpan(Container& cont)
{
    return { cont };
}

template <typename Container>
constexpr Span<typename Container::value_type const> MakeSpan(Container const& cont)
{
    return { cont };
}

} /** namespace core */
} /** namespace ara */

#endif /** !__ARA_CORE_SPAN_H__ */

